mod req;
mod rsp;

use crate::core::entity::device_candidate::DeviceCandidate;
use crate::custom_filter::CustomFilter;
use crate::error::{IntoWithStatus, ToStatusCode};
use crate::router::device_router::req::{PostDeviceCandidateRequest, PostDeviceRequest};
use crate::router::device_router::rsp::GetAllCandidatesResponse;
use crate::router::dto::device_entry::DeviceEntry;
use share::core::device_id::DeviceId;
use snafu::{OptionExt, ResultExt, Snafu};
use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tracing::error;
use warp::http::StatusCode;
use warp::{Filter, Rejection, Reply};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Device entry not found, id: {id}"))]
    NotFound { id: DeviceId },
}

impl ToStatusCode for Error {
    fn status_code(&self) -> StatusCode {
        match self {
            Error::NotFound { .. } => StatusCode::NOT_FOUND,
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

struct Implementation {}

impl Implementation {
    async fn get_all(
        devices: Arc<RwLock<HashMap<DeviceId, Option<String>>>>,
    ) -> Result<Vec<DeviceEntry>> {
        let lock = devices.read().await;

        let result = lock
            .iter()
            .map(|(id, desc)| DeviceEntry {
                id: id.clone(),
                desc: desc.clone(),
            })
            .collect();

        Ok(result)
    }

    async fn add(
        req: PostDeviceRequest,
        devices: Arc<RwLock<HashMap<DeviceId, Option<String>>>>,
    ) -> Result<()> {
        let mut lock = devices.write().await;

        lock.insert(req.id, req.desc);

        Ok(())
    }

    async fn remove(
        id: DeviceId,
        devices: Arc<RwLock<HashMap<DeviceId, Option<String>>>>,
    ) -> Result<()> {
        let mut lock = devices.write().await;

        lock.remove(&id);

        Ok(())
    }

    async fn clear(devices: Arc<RwLock<HashMap<DeviceId, Option<String>>>>) -> Result<()> {
        let mut lock = devices.write().await;

        lock.clear();

        Ok(())
    }

    async fn add_candidate(
        addr: Option<SocketAddr>,
        real_ip: Option<IpAddr>,
        req: PostDeviceCandidateRequest,
        inner_ip: Arc<RwLock<Option<IpAddr>>>,
        candidates: Arc<Mutex<Vec<DeviceCandidate>>>,
    ) -> Result<bool> {
        let inner_ip = inner_ip.read().await;
        let Some(inner_ip) = *inner_ip else {
            return Ok(false);
        };

        let ip = real_ip.or(addr.map(|a| a.ip()));
        let Some(ip) = ip else {
            return Ok(false);
        };

        if ip != inner_ip {
            return Ok(false);
        }

        let mut guard = candidates.lock().await;
        if guard.len() > 50 {
            error!("Too many device candidates");
            return Ok(false);
        }

        guard.push(DeviceCandidate::new(
            req.custom_device_id,
            req.device_id,
            req.channel,
        ));

        Ok(true)
    }

    async fn clear_candidates(candidates: Arc<Mutex<Vec<DeviceCandidate>>>) -> Result<()> {
        candidates.lock().await.clear();

        Ok(())
    }

    async fn get_all_candidates(
        candidates: Arc<Mutex<Vec<DeviceCandidate>>>,
    ) -> Result<GetAllCandidatesResponse> {
        let guard = candidates.lock().await;
        let candidates = guard.iter().cloned().collect();

        Ok(GetAllCandidatesResponse { candidates })
    }
}

struct Router {}

impl Router {
    fn post(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::post()
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.devices())
            .then(move |req: PostDeviceRequest, devices| async {
                Implementation::add(req, devices).await.into_with_status()
            })
    }

    fn delete(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path::param())
            .and(warp::path::end())
            .and(filter.devices())
            .then(move |id: DeviceId, devices| async {
                Implementation::remove(id, devices).await.into_with_status()
            })
    }

    fn clear(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path::path("clear"))
            .and(warp::path::end())
            .and(filter.devices())
            .then(move |devices| async { Implementation::clear(devices).await.into_with_status() })
    }

    fn get_all(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::get()
            .and(warp::path::path("all"))
            .and(warp::path::end())
            .and(filter.devices())
            .then(move |devices| async {
                Implementation::get_all(devices).await.into_with_status()
            })
    }

    fn post_candidate(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::post()
            .and(warp::path::path("candidate"))
            .and(warp::path::end())
            .and(warp::addr::remote())
            .and(warp::header::optional::<IpAddr>("X-Real-Ip"))
            .and(warp::body::json())
            .and(filter.inner_ip())
            .and(filter.device_candidates())
            .then(
                |addr, real_ip, req: PostDeviceCandidateRequest, inner_ip, candidates| async move {
                    Implementation::add_candidate(addr, real_ip, req, inner_ip, candidates)
                        .await
                        .into_with_status()
                },
            )
    }

    fn clear_candidates(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path::path("candidate"))
            .and(warp::path::path("clear"))
            .and(filter.device_candidates())
            .then(|candidates| async {
                Implementation::clear_candidates(candidates)
                    .await
                    .into_with_status()
            })
    }

    fn get_all_candidates(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::get()
            .and(warp::path::path("candidate"))
            .and(warp::path::path("all"))
            .and(filter.device_candidates())
            .then(|candidates| async {
                Implementation::get_all_candidates(candidates)
                    .await
                    .into_with_status()
            })
    }
}

pub fn get_all_router(
    filter: Arc<CustomFilter>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    warp::path::path("device").and(
        Router::post(filter.clone())
            .or(Router::delete(filter.clone()))
            .or(Router::clear(filter.clone()))
            .or(Router::get_all(filter.clone()))
            .or(Router::post_candidate(filter.clone()))
            .or(Router::clear_candidates(filter.clone()))
            .or(Router::get_all_candidates(filter.clone())),
    )
}
